Skip to main content

winsafe\kernel\utilities/
path.rs

1//! File path utilities.
2//!
3//! Some of the functions are similar to [`std::path::Path`] ones, but here they
4//! work directly upon [`&str`](str) instead of [`&OsStr`](std::ffi::OsStr).
5
6use crate::co;
7use crate::decl::*;
8use crate::kernel::iterators::*;
9use crate::prelude::*;
10
11/// Returns an iterator over the files and folders within a directory. Does not
12/// search recursively.
13///
14/// This is a high-level abstraction over [`HFINDFILE`](crate::HFINDFILE)
15/// iteration functions.
16///
17/// # Examples
18///
19/// ```no_run
20/// use winsafe::{self as w, prelude::*};
21///
22/// // Ordinary for loop
23/// for file_path in w::path::dir_list_flat("C:\\Temp") {
24///     let file_path = file_path?;
25///     println!("{}", file_path);
26/// }
27///
28/// // Collecting into a Vec
29/// let all = w::path::dir_list_flat("C:\\Temp")
30///     .collect::<w::SysResult<Vec<_>>>()?;
31///
32/// // Filtering by extension and collecting into a Vec
33/// let txts = w::path::dir_list_flat("C:\\Temp")
34///     .filter(|file_path| match file_path {
35///         Err(_) => true,
36///         Ok(file_path) => w::path::has_extension(file_path, &["txt"]),
37///     })
38///     .collect::<w::SysResult<Vec<_>>>()?;
39///
40/// // Transforming and collecting into a Vec
41/// let all = w::path::dir_list_flat("C:\\Temp")
42///     .map(|file_path| {
43///         let file_path = file_path?;
44///         Ok(format!("PATH: {}", file_path))
45///     })
46///     .collect::<w::SysResult<Vec<_>>>()?;
47/// # w::SysResult::Ok(())
48/// ```
49#[must_use]
50pub fn dir_list_flat<'a>(dir_path: &'a str) -> impl Iterator<Item = SysResult<String>> + 'a {
51	DirListFlatIter::new(dir_path.to_owned())
52}
53
54/// Returns an interator over the files within a directory, and all its
55/// subdirectories, recursively.
56///
57/// This is a high-level abstraction over [`HFINDFILE`](crate::HFINDFILE)
58/// iteration functions.
59///
60/// # Examples
61///
62/// ```no_run
63/// use winsafe::{self as w, prelude::*};
64///
65/// // Ordinary for loop
66/// for file_path in w::path::dir_list_recursive("C:\\Temp") {
67///     let file_path = file_path?;
68///     println!("{}", file_path);
69/// }
70///
71/// // Closure with try_for_each
72/// w::path::dir_list_recursive("C:\\Temp")
73///     .try_for_each(|file_path| {
74///         let file_path = file_path?;
75///         println!("{}", file_path);
76///         Ok(())
77///     })?;
78///
79/// // Collecting into a Vec
80/// let all = w::path::dir_list_recursive("C:\\Temp")
81///     .collect::<w::SysResult<Vec<_>>>()?;
82///
83/// // Transforming and collecting into a Vec
84/// let all = w::path::dir_list_recursive("C:\\Temp")
85///     .map(|file_path| {
86///         let file_path = file_path?;
87///         Ok(format!("PATH: {}", file_path))
88///     })
89///     .collect::<w::SysResult<Vec<_>>>()?;
90/// # w::SysResult::Ok(())
91/// ```
92#[must_use]
93pub fn dir_list_recursive<'a>(dir_path: &'a str) -> impl Iterator<Item = SysResult<String>> + 'a {
94	DirListRecursiveIter::new(dir_path.to_owned())
95}
96
97/// Returns a new string with the path of the current EXE file, without the EXE
98/// filename, and without a trailing backslash.
99///
100/// In a debug build, the `target\debug` folders will be suppressed.
101#[cfg(debug_assertions)]
102#[must_use]
103pub fn exe_path() -> SysResult<String> {
104	let dbg = HINSTANCE::NULL.GetModuleFileName()?;
105	Ok(get_path(
106		get_path(
107			get_path(&dbg).unwrap(), // exe name; go up target and debug folders
108		)
109		.unwrap(),
110	)
111	.unwrap()
112	.to_owned())
113}
114
115/// Returns a new string with the path of the current EXE file, without the EXE
116/// filename, and without a trailing backslash.
117///
118/// In a debug build, the `target\debug` folders will be suppressed.
119#[cfg(not(debug_assertions))]
120#[must_use]
121pub fn exe_path() -> SysResult<String> {
122	Ok(get_path(&HINSTANCE::NULL.GetModuleFileName()?)
123		.unwrap()
124		.to_owned())
125}
126
127/// Returns true if the path exists.
128#[must_use]
129pub fn exists(full_path: &str) -> bool {
130	GetFileAttributes(full_path).is_ok()
131}
132
133/// Extracts the file name from a full path, if any.
134///
135/// # Examples
136///
137/// ```no_run
138/// use winsafe::{self as w, prelude::*};
139///
140/// let f = w::path::get_file_name("C:\\Temp\\foo.txt"); // foo.txt
141/// ```
142#[must_use]
143pub fn get_file_name(full_path: &str) -> Option<&str> {
144	match full_path.rfind('\\') {
145		None => Some(full_path), // if no backslash, the whole string is the file name
146		Some(idx) => {
147			if idx == full_path.chars().count() - 1 {
148				None // last char is '\\', no file name
149			} else {
150				Some(&full_path[idx + 1..])
151			}
152		},
153	}
154}
155
156/// Extracts the full path, but the last part.
157///
158/// # Examples
159///
160/// ```no_run
161/// use winsafe::{self as w, prelude::*};
162///
163/// let p = w::path::get_path("C:\\Temp\\xx\\a.txt"); // C:\Temp\xx
164/// let q = w::path::get_path("C:\\Temp\\xx\\");      // C:\Temp\xx
165/// let r = w::path::get_path("C:\\Temp\\xx");        // C:\Temp"
166/// ```
167#[must_use]
168pub fn get_path(full_path: &str) -> Option<&str> {
169	full_path
170		.rfind('\\') // if no backslash, the whole string is the file name, so no path
171		.map(|idx| &full_path[0..idx])
172}
173
174/// Tells whether the full path ends in one of the given extensions,
175/// case-insensitive.
176///
177/// # Examples
178///
179/// ```no_run
180/// use winsafe::{self as w, prelude::*};
181///
182/// println!("{}",
183///     w::path::has_extension("file.txt", &["txt", "bat"]));
184/// ```
185#[must_use]
186pub fn has_extension(full_path: &str, extensions: &[impl AsRef<str>]) -> bool {
187	let full_path_u = full_path.to_uppercase();
188	extensions
189		.iter()
190		.find(|ext| {
191			let mut ext_upper = ext.as_ref().to_uppercase();
192			if !ext_upper.starts_with('.') {
193				ext_upper.insert(0, '.'); // prepend dot
194			}
195			full_path_u.ends_with(&ext_upper)
196		})
197		.is_some()
198}
199
200/// Returns true if the path is a directory. Calls
201/// [`GetFileAttributes`](crate::GetFileAttributes).
202///
203/// # Panics
204///
205/// Panics if the path does not exist.
206#[must_use]
207pub fn is_directory(full_path: &str) -> bool {
208	let flags = GetFileAttributes(full_path).unwrap();
209	flags.has(co::FILE_ATTRIBUTE::DIRECTORY)
210}
211
212/// Returns true if the path is hidden. Calls
213/// [`GetFileAttributes`](crate::GetFileAttributes).
214///
215/// # Panics
216///
217/// Panics if the path does not exist.
218#[must_use]
219pub fn is_hidden(full_path: &str) -> bool {
220	let flags = GetFileAttributes(full_path).unwrap();
221	flags.has(co::FILE_ATTRIBUTE::HIDDEN)
222}
223
224/// Replaces the file extension by the given one, returning a new string.
225///
226/// # Examples
227///
228/// ```no_run
229/// use winsafe::{self as w, prelude::*};
230///
231/// let p = w::path::replace_extension(
232///     "C:\\Temp\\something.txt", ".sh"); // C:\Temp\something.sh
233/// ```
234#[must_use]
235pub fn replace_extension(full_path: &str, new_extension: &str) -> String {
236	if let Some(last) = full_path.chars().last() {
237		if last == '\\' {
238			return rtrim_backslash(full_path).to_owned(); // full_path is a directory, do nothing
239		}
240	}
241
242	let new_has_dot = new_extension.chars().next() == Some('.');
243	match full_path.rfind('.') {
244		None => format!(
245			"{}{}{}", // file name without extension, just append it
246			full_path,
247			if new_has_dot { "" } else { "." },
248			new_extension,
249		),
250		Some(idx) => {
251			format!("{}{}{}", &full_path[0..idx], if new_has_dot { "" } else { "." }, new_extension,)
252		},
253	}
254}
255
256/// Replaces the file name by the given one, returning a new string.
257#[must_use]
258pub fn replace_file_name(full_path: &str, new_file: &str) -> String {
259	match get_path(full_path) {
260		None => new_file.to_owned(),
261		Some(path) => format!("{}\\{}", path, new_file),
262	}
263}
264
265/// Keeps the file name and replaces the path by the given one, returning a new
266/// string.
267///
268/// # Examples
269///
270/// ```no_run
271/// use winsafe::{self as w, prelude::*};
272///
273/// let p = w::path::replace_path( // C:\another\foo.txt
274///     "C:\\Temp\\foo.txt",
275///     "C:\\another",
276/// );
277/// ```
278#[must_use]
279pub fn replace_path(full_path: &str, new_path: &str) -> String {
280	let file_name = get_file_name(full_path);
281	format!(
282		"{}{}{}",
283		rtrim_backslash(new_path),
284		if file_name.is_some() { "\\" } else { "" },
285		file_name.unwrap_or("")
286	)
287}
288
289/// Removes a trailing backslash, if any.
290///
291/// # Examples
292///
293/// ```no_run
294/// use winsafe::{self as w, prelude::*};
295///
296/// let p = w::path::rtrim_backslash("C:\\Temp\\"); // C:\Temp
297/// ```
298#[must_use]
299pub fn rtrim_backslash(full_path: &str) -> &str {
300	match full_path.chars().last() {
301		None => full_path, // empty string
302		Some(last_ch) => {
303			if last_ch == '\\' {
304				let mut chars = full_path.chars();
305				chars.next_back(); // remove last char
306				chars.as_str()
307			} else {
308				full_path // no trailing backslash
309			}
310		},
311	}
312}
313
314/// Returns a `Vec` with each part of the full path.
315#[must_use]
316pub fn split_parts(full_path: &str) -> Vec<&str> {
317	let no_bs = rtrim_backslash(full_path);
318	no_bs.split('\\').collect()
319}